This notebook demonstrates a basic Seurat workflow using Visium spatial transcriptomics data (10x Genomics). It includes QC, normalization, dimensionality reduction, clustering, and marker discovery.

Libraries

Load the required packages.

library(Seurat) # main package, for spatial transcriptomics analysis
## Loading required package: SeuratObject
## Loading required package: sp
## 
## Attaching package: 'SeuratObject'
## The following objects are masked from 'package:base':
## 
##     intersect, t
library(ggplot2) # for data exploration and quality control visualization
library(dplyr) # data manipulation for filtering and transforming data
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union

Load Data

Load the raw spatial gene expression data from 10x Genomics Visium using the Load10X_Spatial() function.

# path to the folder containing Visium raw data
# (user must download from 10x Genomics)
data_dir <- "data/V1_Adult_Mouse_Brain_Coronal_Section_1"

brain_coronal <- Load10X_Spatial(
  data.dir = data_dir,
  filename = "V1_Adult_Mouse_Brain_Coronal_Section_1_raw_feature_bc_matrix.h5"
)

Quality Control

Now that the data is loaded, let’s do a quick inspection of the structure and quality of the data.

# quick overview of the Seurat object, including the number of features (genes) and observations (spots)
print(brain_coronal)
## An object of class Seurat 
## 32285 features across 4992 samples within 1 assay 
## Active assay: Spatial (32285 features, 0 variable features)
##  1 layer present: counts
##  1 spatial field of view present: slice1
# confirm dimensions of the underlying gene expression matrix
dim(brain_coronal)
## [1] 32285  4992
# summarize the counts and feature information
summary(brain_coronal@meta.data$nCount_Spatial)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##       7    3197    8602    9346   13826   44016
summary(brain_coronal@meta.data$nFeature_Spatial)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##       7    1906    3600    3453    4827    8013
# quick view of the first few rows of the metadata, such as total counts and number of features
head(brain_coronal@meta.data)
# show the distribution of the counts and the features
# useful for detecting outliers or uneven sequencing depth
# the distributions help decide how to filter low-quality spots before normalization
# typical thresholds: 
# nCount_Spatial -> filter out spots with fewer than ~500-1000 total spots
# nFeature_Spatial -> filter out spots with fewer than ~200-500 detected genes
VlnPlot(brain_coronal, features = c("nCount_Spatial", "nFeature_Spatial"))
## Warning: Default search for "data" layer in "Spatial" assay yielded no results;
## utilizing "counts" layer instead.

# optionally save the figure:
# ggsave("figures/qc_violin.png", width = 6, height = 4)

SpatialFeaturePlot(
  brain_coronal,
  features = c("nFeature_Spatial", "nCount_Spatial")
)

# optionally save the figure:
# ggsave("figures/qc_spatial_feature.png", width = 6, height = 4)

Normalization

Normalization adjusts for differences in sequencing depth between spots so that expression values can be meaningfully compared across the tissue. The output of NormalizeData() replaces the raw counts in the Seurat object with a new “normalized” assay that can be used for downstream steps such as finding variable features and scaling.

Why this matters: Normalization ensures that observed differences in gene expression reflect true biological variation, not technical differences in sequencing depth or spot capture efficiency.

# NormalizeData() performs global scaling normalization on the raw count matrix
brain_coronal <- NormalizeData(brain_coronal,
                            # LogNormalize normalizes each spot by:
                            # 1) dividing each gene's counts by the total counts for that spot (to account for sequencing depth)
                            # 2) multiplying by a scale factor (commonly 10,000) to bring all spots to a comparable range
                            # 3) taking the natual log of the result to stabilize variance and reduce the effect of highly expressed genes
                            normalization.method = "LogNormalize",
                            scale.factor = 10000)
## Normalizing layer: counts
# SCTransform() is also a normalization approach, that is often the modern recommendation (still need to look into it further)
# SCTransform() handles the unique technical characteristics for spatial data better (better at handling technical noise and removing confounding variation)
# SCTransform() handles the NormalizeData(), FindVariableFeatures() and ScaleData() workflow in a single call
# For example:
# bbrain_corona <- SCTransform(brain_post, assay = "Spatial")

Variable Features

After normalization, the next step is to identify the genes that show the most variation across spots. These are likely to capture meaningful biological differences rather than background noise.

Why this matters: Not all genes contribute equally to distinguishing cell types or tissue regions (many are stably expressed “housekeeping” events). Focusing on variable genes improves the sensitivity and efficiency of downstream steps like PCA (dimensionality reduction) and clustering, helping reveal the major biological patterns in the spatial data.

# FindVariableFeatures() selects genes with the highest variability across spots, which helps focus the analysis on biologically informative genes
brain_coronal <- FindVariableFeatures(brain_coronal,
                                   # vst (variance-stabilizing transformation) method identifies variable genes efficiently and is the default, robust choice for most datasets
                                   selection.method = "vst",
                                   # keeps the 2,000 most variable genes, a common balance between capturing signal and avoiding noise 
                                   nFeatures = 2000)
## Finding variable features for layer counts

Scaling

Scaling standardizes the expression values of each gene so that all genes contribute equally to downstream analyses like PCA and clustering.

Why this matters: Many statistical methods, including PCA, assuming data are on the same scale. Scaling removes technical bias and helps PCA focus on the most biological informative variation rather than raw expression magnitude.

# ScaleData() centers and scale the expression value for each gene across all spots
# Centering: subtracts the mean expression of each gene, setting the mean to zero
# Scaling: divides by the standard deviation, giving each gene a variance of one
brain_coronal <- ScaleData(brain_coronal)
## Centering and scaling data matrix

PCA

Next, we can reduce the dataset’s dimensionality using PCA.

Why this matters: PCA helps uncover the major axes of variation in the dataset, patterns that distinguish groups of spots or tissue regions based on their gene expression. It transforms the data into a space where the first few PCs capture the strongest sources of biological signal (e.g., cell type or spatial region), while minimaizng noise.

# RunPCA() performs linear dimensionality reduction, summarizing thousands of gene expression values into a smaller number of composite variables called principal compnents (PCs)
brain_coronal <- RunPCA(brain_coronal,
                     # feaures argument specifies that PCA should be run only on the most variable genes, since those drive the most informative biological variation
                     features = VariableFeatures(brain_coronal),
                     # FALSE simply hides progress messages for cleaner output
                     verbose = FALSE)

# Access PCA loadings and embeddings
# pca_loadings represents gene weights (contribution to PCAs)
pca_loadings <- brain_coronal[["pca"]]@feature.loadings
# rows = genes, columns = PCAs
head(pca_loadings)
##              PC_1         PC_2         PC_3         PC_4        PC_5
## Ttr   0.031018087  0.017415255  0.051226434 -0.001171140 -0.02166846
## Pmch  0.021073717 -0.055476750 -0.025630549  0.095768863  0.02598763
## Enpp2 0.037487297 -0.019857957  0.063521053 -0.032816893  0.01927288
## Hcrt  0.014126893 -0.042474982 -0.016333995  0.103014488  0.02746131
## Avp   0.006064305  0.006077583 -0.004322152  0.041971406 -0.00670275
## Ecrg4 0.032982831 -0.002235661  0.072812812  0.000822668 -0.03535360
##              PC_6         PC_7         PC_8        PC_9        PC_10
## Ttr   -0.03427002 -0.015825790  0.126178084  0.01828716  0.091877876
## Pmch   0.01155156  0.014302012 -0.002887082  0.02386162 -0.103716593
## Enpp2 -0.01152483 -0.053507517  0.072794371 -0.01034420  0.009688989
## Hcrt   0.01739926  0.011431520 -0.004101998  0.04596314 -0.093599348
## Avp    0.04213440 -0.002766944 -0.024066193  0.02177207 -0.078811825
## Ecrg4 -0.03191450 -0.018453839  0.092750785 -0.02453343  0.008826186
##             PC_11        PC_12       PC_13         PC_14       PC_15
## Ttr   -0.02810991  0.050509331 -0.06651905  0.0376770218  0.04696401
## Pmch   0.02566208 -0.012637485 -0.03619048  0.0004758694 -0.02214047
## Enpp2 -0.02174247 -0.021322277 -0.03598413 -0.0292159255  0.02874004
## Hcrt   0.02704272 -0.023475183 -0.04880918 -0.0057433047 -0.02700731
## Avp    0.02205090 -0.028465760 -0.08686791 -0.0919525267 -0.04872711
## Ecrg4 -0.04907405  0.006937494 -0.01837888 -0.0148007161  0.00754818
##              PC_16        PC_17        PC_18        PC_19        PC_20
## Ttr    0.061910249  0.025125171  0.012801549  0.037411376  0.029896715
## Pmch  -0.010391252  0.017401439 -0.005677884  0.013722325  0.003716309
## Enpp2  0.016105371 -0.019432937 -0.003231383  0.021003946  0.030184705
## Hcrt  -0.030938024  0.017756874 -0.011740231  0.005801678 -0.020307039
## Avp    0.022185410 -0.034324677 -0.034612348 -0.039545178 -0.034087364
## Ecrg4  0.008031878 -0.002643199 -0.010294321 -0.006029953 -0.005676400
##              PC_21       PC_22       PC_23         PC_24      PC_25       PC_26
## Ttr    0.051030456 -0.07570672 -0.07285434 -0.0082045849 0.08262555 0.059232843
## Pmch   0.066288428  0.02330912 -0.06060674  0.0432099226 0.02971535 0.004655827
## Enpp2 -0.005817855 -0.02887003 -0.03705906  0.0007611628 0.09292993 0.037848889
## Hcrt   0.075632623  0.02335345 -0.04758655  0.0369840770 0.02445445 0.011854353
## Avp    0.145774449  0.04923176  0.08760996  0.0546857305 0.08034484 0.040011655
## Ecrg4  0.019316652 -0.03930896 -0.02387991  0.0070416654 0.02982076 0.015803994
##              PC_27       PC_28        PC_29         PC_30        PC_31
## Ttr   -0.046089470 -0.01174974  0.047127446 -0.0727179780 -0.023226095
## Pmch   0.007751021 -0.05911426  0.011322214 -0.0191181128  0.009334538
## Enpp2 -0.032050458 -0.02094597  0.034050332 -0.0560680813 -0.025157172
## Hcrt   0.008177762 -0.04006891 -0.002843259 -0.0203968797  0.018453756
## Avp    0.091063098  0.01941600 -0.049303102 -0.0001183483 -0.121176652
## Ecrg4 -0.019096801 -0.01032106  0.008295715 -0.0322552416 -0.004543280
##              PC_32       PC_33        PC_34       PC_35        PC_36
## Ttr   -0.093394990  0.02016739  0.014293814 -0.03432793  0.009437433
## Pmch   0.001248338 -0.01835385  0.022277955  0.01942333  0.006354238
## Enpp2 -0.028336267  0.03083901 -0.002599355 -0.04760938  0.003976287
## Hcrt   0.002158477 -0.01149721  0.008624773  0.02428038 -0.001344962
## Avp    0.034858770 -0.03580898 -0.021093145 -0.11469557  0.023628763
## Ecrg4 -0.051904356  0.01726138 -0.026020111 -0.01564848 -0.002405795
##              PC_37        PC_38        PC_39         PC_40         PC_41
## Ttr   -0.018046741 -0.028967712 -0.020872537 -0.0039740843  0.0393835264
## Pmch  -0.006300191 -0.001185861 -0.002324966  0.0212031331  0.0087093321
## Enpp2  0.008237755 -0.014423597  0.003089571  0.0015070283 -0.0005193595
## Hcrt  -0.013156239 -0.007823580  0.002501898  0.0006855703 -0.0144778442
## Avp   -0.011653162  0.075839163  0.005985841 -0.0047122100 -0.0226774919
## Ecrg4 -0.002711343  0.005031402  0.013460149 -0.0109571999  0.0387477824
##              PC_42        PC_43         PC_44       PC_45        PC_46
## Ttr    0.012088490 -0.027086583  9.110482e-03  0.04163426  0.019074489
## Pmch   0.001159198  0.012531604  1.871878e-02  0.01364158  0.020228659
## Enpp2 -0.001212787 -0.004197455 -1.621352e-02  0.02771543  0.004655701
## Hcrt   0.005324007  0.017894059 -2.315538e-05 -0.00486177  0.022403013
## Avp    0.004256243  0.049756293  6.987671e-03 -0.03641091 -0.012874347
## Ecrg4 -0.001787317 -0.007095159  8.039253e-03  0.01120796  0.011076524
##              PC_47         PC_48        PC_49        PC_50
## Ttr   -0.029622700  0.0002927371  0.008753503 -0.009484642
## Pmch   0.002302696  0.0015773601  0.008783938  0.010809201
## Enpp2  0.005445809 -0.0174724343  0.027182109 -0.007653798
## Hcrt  -0.018405136 -0.0007329584  0.011803052  0.006033177
## Avp   -0.008905139  0.0003505590 -0.039973694  0.004964869
## Ecrg4  0.012700716 -0.0052415880  0.025642530  0.010358548
# pca_embenndings represents PCA-transformed coordinates for each spot/cell
pca_embeddings <- Embeddings(brain_coronal, reduction = "pca")
# rows = spots or cells, columns = PCAs
# head(pca_embeddings)

Once PCA is complete, we can visualize how the spatial spots are distributed in the new, reduced-dimensionality space. This visualization helps you see whether biological structure (like distinct tissue regions or cell populations) is already emerging in the data.

# DimPlot() creates a scatter plot showing the positions of each spot in the selected dimensional reduction space
# reduction = "pca" tells Seurat to plot the first two princiapl components (PC1 and PC2)
DimPlot(brain_coronal, reduction = "pca")

# optionally save the figure:
# ggsave("figures/dim_pca.png", width = 6, height = 4)

To decide how many how many principal components (PCs) capture meaningful biological variation, we use the elbow plot. The x-axis shows the PC number and the y-axis shows the standard deviation or variability captured by each PC. The curve typically drops sharply at first and then levels off, forming an “elbow” shape. The “elbow plot” marks where additional PCs contribute diminishing returns, meaning they mostly capture noise rather than signal. Choose the number of PCs just before the curve flattens out.

Why this matters: Selecting an appriopriate number of PCs ensures that downstream analyses (like clustering and spatial embedding) are driven by true biological structure, not random variability.

# ElbowPlot() visualizes the variance explained by each principal component
ElbowPlot(brain_coronal, ndims = 50)

# optionally save the figure:
# ggsave("figures/elbow.png", width = 6, height = 4)

UMAP

After identifying the key principal components, we can visualize the data in a lower-dimensional space using UMAP (Uniform Manifold Approximation and Projection).

Why this matters: UMAP preserves local and global structure, meaning it keeps nearby spots close together while maintaining the broader relationships between tissue regions.

UMAP reveals complex, non-linear relationships, often corresponding to meaningful biological structures,

# RunUMAP() performs non-linear dimensionality reduction, mapping high-dimenional expression data.
brain_coronal <- RunUMAP(brain_coronal,
                      # the argument dims = 1:40 specifies that UMAP should use the first 40 principal components
                      dims = 1:40,
                      # FALSE simply hides progress messages for cleaner output
                      verbose = FALSE)
## Warning: The default method for RunUMAP has changed from calling Python UMAP via reticulate to the R-native UWOT using the cosine metric
## To use Python UMAP via reticulate, set umap.method to 'umap-learn' and metric to 'correlation'
## This message will be shown once per session

Once UMAP has been computed, we can plot the results to visualize how spots group based on their gene expression profiles. Distinct clusters or regions in the UMAP often corresponds to different cell types, tissue regions, or biological states, giving an immediate visual sense of structure within the data.

# DimPlot() displays each spatial spot as a point in UMAP space
# reduciton = "umap" specifies that the plot should use UMAP coordinates
DimPlot(brain_coronal, reduction = "umap")

# optionally save the figure:
# ggsave("figures/dim_umap.png", width = 6, height = 4)

Clustering

Now we can identify groups of spatial spots within similar gene expression patterns, a process known as clustering.

Why this matters: This step lays the foundation for identifying spatial domains or cell-types-like region in the tissue.

# FindNeighbors() builds a nearest-neighbor graph where each spot is connected to others with similar expression profiles (based on the first 40 principal components)
brain_coronal <- FindNeighbors(brain_coronal,
                           # the argument dims = 1:40 specifies that UMAP should use the first 40 principal components
                           dims = 1:40,
                           # FALSE simply hides progress messages for cleaner output
                           verbose = FALSE)

After building the neighbor graph, we now group spots into clusters based on their shared gene expression patterns.

Why this matters: These clusters often correspond to biological meaningful regions or cell-type domains with the tissue.

# FindClusters() was the shared nearest neighbor (SNN) graph from the previous step to detect communities of similar spots
brain_coronal <- FindClusters(brain_coronal,
                           # resolution controls the granularity of clustering
                           # lower values -> fewer, broader clusters
                           # higher values -> more, finer clusters
                           resolution = 0.5,
                           # FALSE simply hides progress messages for cleaner output
                           verbose = FALSE)

Now that clusters have been identified, we can visualize them directly on the tissue image to see how transcriptional domain align with physical anatomy.

Why this matters: This plots connects gene expression patterns to their spatial context, allowing us to see how transcriptionally distinct regions map onto real tissue features.

# SpatialDimPlot() overlays the cluster identities onto the histological tissue image
# each spot is colored by its assigned cluster, showing where distinct molecular regions occur spatially
# label = TRUE adds cluster labels at the center of each group for clarity
# label.size adjust the text size
# image.alpha sets the image transparency
SpatialDimPlot(brain_coronal, label = TRUE, label.size = 5, image.alpha = 0.5)
## Scale for fill is already present.
## Adding another scale for fill, which will replace the existing scale.

# optionally save the figure:
# ggsave("figures/spatial_dim.png", width = 6, height = 4)

Marker Discovery

After clustering, we can identify the marker genes that best characterize each cluster, genes that are significantly more expressed in one cluster than in others.

Why this matters: Marker genes help biologically annotate clusters, revealing which clusters correspond to known cell types or tissue layers.

Note: This takes a few minutes to run.

# FindAllMarkers() performs diffreential expression analysis across all clusters to find genes that define each one
markers_coronal <- FindAllMarkers(brain_coronal,
                               # only.pos = TRUE restricts the ouput to positively expressed markers (genes upregulated in a cluster compared to others)
                               only.pos = TRUE,
                               # min.pct = 0.25 filters to include only genes expressed in at least 25% of spots within the cluster, ensuring markers are broadly representative rather than rare outliers
                               min.pct = 0.25,
                               # logfc.threshold = 0.25 keeps genes with at least 0.25 log fold-change in expression, focusing on biologically meaningful differences
                               logfc.threshold = 0.25,
                               # FALSE simply hides progress messages for cleaner output
                               verbose = FALSE)
## For a (much!) faster implementation of the Wilcoxon Rank Sum Test,
## (default method for FindMarkers) please install the presto package
## --------------------------------------------
## install.packages('devtools')
## devtools::install_github('immunogenomics/presto')
## --------------------------------------------
## After installation of presto, Seurat will automatically use the more 
## efficient implementation (no further action necessary).
## This message will be shown once per session

After idenitfying all marker genes, it’s useful to extract the top markers for each cluster, those showing the strongest differential expression.

Why this matters: Focusing on the top few markser simplifies interpretation and downstream visualizationm these genes are typically the most characteristic of each spatial cluster. They can later be used for annotation, plotting, or validatiing known tissue-specific signatures.

top_markers_coronal <- markers_coronal |>
  # group all marker genes by their cluster identity
  group_by(cluster) |>
  # select the five genes per cluster with the highest average log2 fold change, meaning the strongest upregulation relative to other clusters
  top_n(n = 5, wt = avg_log2FC)

# display the reduced table for quick inspection
print(top_markers_coronal)
## # A tibble: 75 × 7
## # Groups:   cluster [15]
##        p_val avg_log2FC pct.1 pct.2 p_val_adj cluster gene  
##        <dbl>      <dbl> <dbl> <dbl>     <dbl> <fct>   <chr> 
##  1 3.17e-106       1.99 0.693 0.492 1.02e-101 0       Cabp1 
##  2 3.10e-105       1.78 0.717 0.544 1.00e-100 0       Stx1a 
##  3 1.52e- 39       1.82 0.477 0.35  4.90e- 35 0       Kcnh3 
##  4 1.53e- 37       1.66 0.489 0.36  4.95e- 33 0       Lamp5 
##  5 1.33e-  3       1.59 0.252 0.252 1   e+  0 0       Cd34  
##  6 1.48e- 54       1.58 0.136 0.609 4.79e- 50 1       Rnd2  
##  7 4.99e- 41       2.14 0.111 0.49  1.61e- 36 1       Cnrip1
##  8 9.04e- 27       1.38 0.057 0.298 2.92e- 22 1       Iqsec3
##  9 1.61e- 23       1.52 0.078 0.316 5.18e- 19 1       Dnal4 
## 10 8.43e- 23       2.21 0.184 0.518 2.72e- 18 1       Polr2i
## # ℹ 65 more rows

Specific Visualization

To explore how individual genes are expressed across the tissue, we can plot their spatial expression patterns directly on the histological image.

Why this matters: Theses plots reveal the spatial distribution of gene activity, showing which tissue rergions or clusters express particular genes. It’s a powerful way to connect molecular identity to anatomy, helping confirm or discover biologically meaningful spatial patterns.

# SpatialFeaturePlot() overlays gene expression levels for selected genes onto the tissue image
SpatialFeaturePlot(brain_coronal, 
                   # features lists the genes of interest to plot
                   features = c("Lamp5", "Lypd1"),
                   # arranges the resulting plot into 2 column grid
                   ncol = 2)

# optionally save the figure:
# ggsave("figures/spatial_feature.png", width = 6, height = 4)

Session Info

# session info
sessioninfo::session_info()
## ─ Session info ───────────────────────────────────────────────────────────────
##  setting  value
##  version  R version 4.5.1 (2025-06-13)
##  os       macOS Tahoe 26.1
##  system   aarch64, darwin24.4.0
##  ui       unknown
##  language (EN)
##  collate  en_US.UTF-8
##  ctype    en_US.UTF-8
##  tz       America/New_York
##  date     2025-11-23
##  pandoc   3.6.3 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/aarch64/ (via rmarkdown)
##  quarto   1.7.32 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/quarto
## 
## ─ Packages ───────────────────────────────────────────────────────────────────
##  package          * version date (UTC) lib source
##  abind              1.4-8   2024-09-12 [1] CRAN (R 4.5.1)
##  beeswarm           0.4.0   2021-06-01 [1] CRAN (R 4.5.1)
##  bit                4.6.0   2025-03-06 [1] CRAN (R 4.5.1)
##  bit64              4.6.0-1 2025-01-16 [1] CRAN (R 4.5.1)
##  bslib              0.9.0   2025-01-30 [1] CRAN (R 4.5.1)
##  cachem             1.1.0   2024-05-16 [1] CRAN (R 4.5.1)
##  cli                3.6.5   2025-04-23 [1] CRAN (R 4.5.1)
##  cluster            2.1.8.1 2025-03-12 [2] CRAN (R 4.5.1)
##  codetools          0.2-20  2024-03-31 [2] CRAN (R 4.5.1)
##  cowplot            1.2.0   2025-07-07 [1] CRAN (R 4.5.1)
##  data.table         1.17.8  2025-07-10 [1] CRAN (R 4.5.1)
##  deldir             2.0-4   2024-02-28 [1] CRAN (R 4.5.1)
##  digest             0.6.37  2024-08-19 [1] CRAN (R 4.5.1)
##  dotCall64          1.2     2024-10-04 [1] CRAN (R 4.5.1)
##  dplyr            * 1.1.4   2023-11-17 [1] CRAN (R 4.5.1)
##  evaluate           1.0.5   2025-08-27 [1] CRAN (R 4.5.1)
##  farver             2.1.2   2024-05-13 [1] CRAN (R 4.5.1)
##  fastDummies        1.7.5   2025-01-20 [1] CRAN (R 4.5.1)
##  fastmap            1.2.0   2024-05-15 [1] CRAN (R 4.5.1)
##  fitdistrplus       1.2-4   2025-07-03 [1] CRAN (R 4.5.1)
##  future           * 1.67.0  2025-07-29 [1] CRAN (R 4.5.1)
##  future.apply       1.20.0  2025-06-06 [1] CRAN (R 4.5.1)
##  generics           0.1.4   2025-05-09 [1] CRAN (R 4.5.1)
##  ggbeeswarm         0.7.2   2023-04-29 [1] CRAN (R 4.5.1)
##  ggplot2          * 4.0.0   2025-09-11 [1] CRAN (R 4.5.1)
##  ggrastr            1.0.2   2023-06-01 [1] CRAN (R 4.5.1)
##  ggrepel            0.9.6   2024-09-07 [1] CRAN (R 4.5.1)
##  ggridges           0.5.7   2025-08-27 [1] CRAN (R 4.5.1)
##  globals            0.18.0  2025-05-08 [1] CRAN (R 4.5.1)
##  glue               1.8.0   2024-09-30 [1] CRAN (R 4.5.1)
##  goftest            1.2-3   2021-10-07 [1] CRAN (R 4.5.1)
##  gridExtra          2.3     2017-09-09 [1] CRAN (R 4.5.1)
##  gtable             0.3.6   2024-10-25 [1] CRAN (R 4.5.1)
##  hdf5r              1.3.12  2025-01-20 [1] CRAN (R 4.5.1)
##  htmltools          0.5.8.1 2024-04-04 [1] CRAN (R 4.5.1)
##  htmlwidgets        1.6.4   2023-12-06 [1] CRAN (R 4.5.1)
##  httpuv             1.6.16  2025-04-16 [1] CRAN (R 4.5.1)
##  httr               1.4.7   2023-08-15 [1] CRAN (R 4.5.1)
##  ica                1.0-3   2022-07-08 [1] CRAN (R 4.5.1)
##  igraph             2.2.1   2025-10-27 [1] CRAN (R 4.5.1)
##  irlba              2.3.5.1 2022-10-03 [1] CRAN (R 4.5.1)
##  jquerylib          0.1.4   2021-04-26 [1] CRAN (R 4.5.1)
##  jsonlite           2.0.0   2025-03-27 [1] CRAN (R 4.5.1)
##  KernSmooth         2.23-26 2025-01-01 [2] CRAN (R 4.5.1)
##  knitr              1.50    2025-03-16 [1] CRAN (R 4.5.1)
##  labeling           0.4.3   2023-08-29 [1] CRAN (R 4.5.1)
##  later              1.4.4   2025-08-27 [1] CRAN (R 4.5.1)
##  lattice            0.22-7  2025-04-02 [2] CRAN (R 4.5.1)
##  lazyeval           0.2.2   2019-03-15 [1] CRAN (R 4.5.1)
##  lifecycle          1.0.4   2023-11-07 [1] CRAN (R 4.5.1)
##  limma              3.64.3  2025-08-03 [1] Bioconductor 3.21 (R 4.5.1)
##  listenv            0.10.0  2025-11-02 [1] CRAN (R 4.5.1)
##  lmtest             0.9-40  2022-03-21 [1] CRAN (R 4.5.1)
##  magrittr           2.0.4   2025-09-12 [1] CRAN (R 4.5.1)
##  MASS               7.3-65  2025-02-28 [2] CRAN (R 4.5.1)
##  Matrix             1.7-4   2025-08-28 [2] CRAN (R 4.5.1)
##  matrixStats        1.5.0   2025-01-07 [1] CRAN (R 4.5.1)
##  mime               0.13    2025-03-17 [1] CRAN (R 4.5.1)
##  miniUI             0.1.2   2025-04-17 [1] CRAN (R 4.5.1)
##  nlme               3.1-168 2025-03-31 [2] CRAN (R 4.5.1)
##  otel               0.2.0   2025-08-29 [1] CRAN (R 4.5.1)
##  parallelly         1.45.1  2025-07-24 [1] CRAN (R 4.5.1)
##  patchwork          1.3.2   2025-08-25 [1] CRAN (R 4.5.1)
##  pbapply            1.7-4   2025-07-20 [1] CRAN (R 4.5.1)
##  pillar             1.11.1  2025-09-17 [1] CRAN (R 4.5.1)
##  pkgconfig          2.0.3   2019-09-22 [1] CRAN (R 4.5.1)
##  plotly             4.11.0  2025-06-19 [1] CRAN (R 4.5.1)
##  plyr               1.8.9   2023-10-02 [1] CRAN (R 4.5.1)
##  png                0.1-8   2022-11-29 [1] CRAN (R 4.5.1)
##  polyclip           1.10-7  2024-07-23 [1] CRAN (R 4.5.1)
##  progressr          0.17.0  2025-10-15 [1] CRAN (R 4.5.1)
##  promises           1.5.0   2025-11-01 [1] CRAN (R 4.5.1)
##  purrr              1.1.0   2025-07-10 [1] CRAN (R 4.5.1)
##  R6                 2.6.1   2025-02-15 [1] CRAN (R 4.5.1)
##  RANN               2.6.2   2024-08-25 [1] CRAN (R 4.5.1)
##  RColorBrewer       1.1-3   2022-04-03 [1] CRAN (R 4.5.1)
##  Rcpp               1.1.0   2025-07-02 [1] CRAN (R 4.5.1)
##  RcppAnnoy          0.0.22  2024-01-23 [1] CRAN (R 4.5.1)
##  RcppHNSW           0.6.0   2024-02-04 [1] CRAN (R 4.5.1)
##  reshape2           1.4.4   2020-04-09 [1] CRAN (R 4.5.1)
##  reticulate         1.44.0  2025-10-25 [1] CRAN (R 4.5.1)
##  rlang              1.1.6   2025-04-11 [1] CRAN (R 4.5.1)
##  rmarkdown          2.30    2025-09-28 [1] CRAN (R 4.5.1)
##  ROCR               1.0-11  2020-05-02 [1] CRAN (R 4.5.1)
##  RSpectra           0.16-2  2024-07-18 [1] CRAN (R 4.5.1)
##  rstudioapi         0.17.1  2024-10-22 [1] CRAN (R 4.5.1)
##  Rtsne              0.17    2023-12-07 [1] CRAN (R 4.5.1)
##  S7                 0.2.0   2024-11-07 [1] CRAN (R 4.5.1)
##  sass               0.4.10  2025-04-11 [1] CRAN (R 4.5.1)
##  scales             1.4.0   2025-04-24 [1] CRAN (R 4.5.1)
##  scattermore        1.2     2023-06-12 [1] CRAN (R 4.5.1)
##  sctransform        0.4.2   2025-04-30 [1] CRAN (R 4.5.1)
##  sessioninfo        1.2.3   2025-02-05 [1] CRAN (R 4.5.1)
##  Seurat           * 5.3.1   2025-10-29 [1] CRAN (R 4.5.1)
##  SeuratObject     * 5.2.0   2025-08-27 [1] CRAN (R 4.5.1)
##  shiny              1.11.1  2025-07-03 [1] CRAN (R 4.5.1)
##  sp               * 2.2-0   2025-02-01 [1] CRAN (R 4.5.1)
##  spam               2.11-1  2025-01-20 [1] CRAN (R 4.5.1)
##  spatstat.data      3.1-9   2025-10-18 [1] CRAN (R 4.5.1)
##  spatstat.explore   3.5-3   2025-09-22 [1] CRAN (R 4.5.1)
##  spatstat.geom      3.6-0   2025-09-20 [1] CRAN (R 4.5.1)
##  spatstat.random    3.4-2   2025-09-21 [1] CRAN (R 4.5.1)
##  spatstat.sparse    3.1-0   2024-06-21 [1] CRAN (R 4.5.1)
##  spatstat.univar    3.1-4   2025-07-13 [1] CRAN (R 4.5.1)
##  spatstat.utils     3.2-0   2025-09-20 [1] CRAN (R 4.5.1)
##  statmod            1.5.1   2025-10-09 [1] CRAN (R 4.5.1)
##  stringi            1.8.7   2025-03-27 [1] CRAN (R 4.5.1)
##  stringr            1.5.2   2025-09-08 [1] CRAN (R 4.5.1)
##  survival           3.8-3   2024-12-17 [2] CRAN (R 4.5.1)
##  tensor             1.5.1   2025-06-17 [1] CRAN (R 4.5.1)
##  tibble             3.3.0   2025-06-08 [1] CRAN (R 4.5.1)
##  tidyr              1.3.1   2024-01-24 [1] CRAN (R 4.5.1)
##  tidyselect         1.2.1   2024-03-11 [1] CRAN (R 4.5.1)
##  utf8               1.2.6   2025-06-08 [1] CRAN (R 4.5.1)
##  uwot               0.2.3   2025-02-24 [1] CRAN (R 4.5.1)
##  vctrs              0.6.5   2023-12-01 [1] CRAN (R 4.5.1)
##  vipor              0.4.7   2023-12-18 [1] CRAN (R 4.5.1)
##  viridisLite        0.4.2   2023-05-02 [1] CRAN (R 4.5.1)
##  withr              3.0.2   2024-10-28 [1] CRAN (R 4.5.1)
##  xfun               0.54    2025-10-30 [1] CRAN (R 4.5.1)
##  xtable             1.8-4   2019-04-21 [1] CRAN (R 4.5.1)
##  yaml               2.3.10  2024-07-26 [1] CRAN (R 4.5.1)
##  zoo                1.8-14  2025-04-10 [1] CRAN (R 4.5.1)
## 
##  [1] /opt/homebrew/lib/R/4.5/site-library
##  [2] /opt/homebrew/Cellar/r/4.5.1/lib/R/library
##  * ── Packages attached to the search path.
## 
## ──────────────────────────────────────────────────────────────────────────────